import SVGScaler from "../Preprocessors/SVGScaler";
import SVGSizer from "../Preprocessors/SVGSizer";
import { ChartSetting, LegendInner, Line, MinMaxPoints, XAxis, YAxis } from "../typeSC";
import { Point } from "../type";
import AxisUtils from "./utils/AxisUtils";
import Utils from "./utils/Utils";
import pixelWidth from 'string-pixel-width';
import * as d3 from 'd3';

export default class ChartCreator {
    chartSetting: ChartSetting;
    svgScaler: SVGScaler;
    svgSizer: SVGSizer;

    svg: any;
    el: any;
    position: Point;

    constructor(svg: any, chartSetting: ChartSetting, svgSizer: SVGSizer, svgScaler: SVGScaler) {
        this.svg = svg;

        this.chartSetting = chartSetting;
        this.svgScaler = svgScaler;
        this.svgSizer = svgSizer;

        this.position = svgSizer.getPosition(chartSetting.id);

        this.el = svg.append('g').attr("data-type", chartSetting.id).attr('transform', `translate(${this.position?.x ?? 0}, ${this.position?.y ?? 0})`);
    }

    draw() {
        for (let chartElement of this.chartSetting['chart_elements'].toReversed()) {
            switch (chartElement.type) {
                case 'XAxis': {
                    this.addXAxis(chartElement);
                    break;
                };
                case 'YAxis': {
                    this.addYAxis(chartElement);
                    break;
                };
                case 'Line': {
                    this.addLine(chartElement);
                    break;
                };
                case 'MixMaxPoints': {
                    this.addMinMaxPoints(chartElement);
                    break;
                };
                case 'LegendInner': {
                    this.addLegendInner(chartElement);
                    break;
                };
            }
        }
    }

    addXAxis(chartElement: XAxis) {
        const scalers = this.svgScaler.getScalers(chartElement['scale_id']);

        if (scalers === null) return;

        const font = chartElement['font_family'] ?? 'times new roman';
        const size = chartElement['font_size'] ?? 12;
        const weight = chartElement['font_weight'] ?? 300;

        const xScaler = scalers.x([0, this.chartSetting.width]);

        const axis = d3.axisBottom(xScaler);

        AxisUtils.setTicks(axis, xScaler, chartElement);
    
        AxisUtils.setTickFormatter(axis, chartElement);

        const axisEl = this.el.append('g')
            .attr("data-type", "xAxis")
            .attr('transform', `translate(${0}, ${this.chartSetting.height})`)
            .call(axis);

        if (chartElement['remove_domain']) axisEl.call((g: any) => g.selectAll('.domain').remove());

        if (chartElement['remove_line']) axisEl.call((g: any) => g.selectAll('line').remove());

        if (chartElement['horizontal_line']) axisEl.call((g: any) => g.selectAll('g.tick').append('line').attr('stroke', 'currentColor').attr("stroke-width", 0.5).attr('y2', -this.chartSetting.height - 20));

        if (font) axisEl.call((g: any) => g.selectAll('text').attr('font-family', font));
        if (size) axisEl.call((g: any) => g.selectAll('text').attr('font-size', size));
        if (weight) axisEl.call((g: any) => g.selectAll('text').attr('font-weight', weight));

        if (chartElement['label']) axisEl.append('text')
            .text(chartElement['label'])
            .attr('class', 'draggable')
            .attr('font-size', size)
            .attr('font-family', font)
            .attr('font-weight', 'bold')
            .attr("transform", `translate(${-pixelWidth(chartElement['label'], { font: font, bold: true, size: size }) - 20}, ${0})`)
            .style("text-anchor", "start")
            .attr("fill", 'black');

    }

    addYAxis(chartElement: YAxis) {
        const scalers = this.svgScaler.getScalers(chartElement['scale_id']);

        if (scalers === null) return;
        
        const font = chartElement['font_family'] ?? 'times new roman';
        const size = chartElement['font_size'] ?? 12;
        const weight = chartElement['font_weight'] ?? 300;

        const yScaler = scalers.y([0, this.chartSetting.height]);

        const axis = d3.axisLeft(yScaler);

        AxisUtils.setTicks(axis, yScaler, chartElement);
    
        AxisUtils.setTickFormatter(axis, chartElement);

        const axisEl = this.el.append('g').attr("data-type", "yAxis")
            .attr('transform', `translate(${0}, ${0})`)
            .call(axis);

        if (chartElement['remove_domain']) axisEl.call((g: any) => g.selectAll('.domain').remove());

        if (chartElement['remove_line']) axisEl.call((g: any) => g.selectAll('line').remove());

        if (chartElement['horizontal_line']) axisEl.call((g: any) => g.selectAll('g.tick').append('line').attr('stroke', 'currentColor').attr("stroke-width", 0.5).attr('x2', this.chartSetting.width));

        if (font) axisEl.call((g: any) => g.selectAll('text').attr('font-family', font));
        if (size) axisEl.call((g: any) => g.selectAll('text').attr('font-size', size));
        if (weight) axisEl.call((g: any) => g.selectAll('text').attr('font-weight', weight));

        if (chartElement['label']) axisEl.append('text')
            .text(chartElement['label'])
            .attr('class', 'draggable')
            .attr('font-size', size)
            .attr('font-family', font)
            .attr('font-weight', 'bold')
            .attr("transform", `translate(${-pixelWidth(chartElement['label'], { font: font, bold: true, size: size }) - 10}, ${-15})`)
            .style("text-anchor", "start")
            .attr("fill", 'black');
    }

    addLine(chartElement: Line) {
        const scalersForX = this.svgScaler.getScalers(chartElement['scale_x_id']);
        const scalersForY = this.svgScaler.getScalers(chartElement['scale_y_id']);
        const chartData = this.svgScaler.getData(chartElement['data_id']);

        if (!scalersForX || !scalersForY || !chartData) return;

        const height = this.chartSetting.height;
        const width = this.chartSetting.width;

        const xScaler = scalersForX.x([0, width]);
        const yScaler = scalersForY.y([0, height]);

        const line = d3.line<{ x: any, y: any }>().x(d => xScaler(d.x)).y(d => yScaler(d.y));

        this.el.append('g').attr("data-type", "line")
            .attr('transform', `translate(${0}, ${0})`)
            .append("path")
            .attr("fill", "none")
            .attr("stroke", chartElement.color ?? 'red')
            .attr("stroke-width", chartElement.strokeWidth ?? 1.5)
            .attr("d", line(chartData.data));
    }

    addMinMaxPoints(chartElement: MinMaxPoints) {
        const scalersForX = this.svgScaler.getScalers(chartElement['scale_x_id']);
        const scalersForY = this.svgScaler.getScalers(chartElement['scale_y_id']);
        const chartData = this.svgScaler.getData(chartElement['data_id']);

        if (!scalersForX || !scalersForY || !chartData) return;

        const font = chartElement['font_family'] ?? 'times new roman';
        const size = chartElement['font_size'] ?? 12;
        const weight = chartElement['font_weight'] ?? 300;

        const height = this.chartSetting.height;
        const width = this.chartSetting.width;

        const xScaler = scalersForX.x([0, width]);
        const yScaler = scalersForY.y([0, height]);

        const { min, max } = Utils.getMinMaxForMonth(chartData.data, 'y', 'x') as { min: { x: any, y: any }[], max: { x: any, y: any }[] };

        this.el.append('g').attr("data-type", "maxPoints")
            .call((g: any) => 
                g.selectAll("max").data(min)
                    .join('circle')
                    .attr("cx", (d: any) => xScaler(d.x))
                    .attr("cy", (d: any) => yScaler(d.y))
                    .attr("r", chartElement['radius'] ?? 2.5)
                    .attr("fill", chartElement['color'] ?? 'red')
            )
            .call((g: any) => 
                g.selectAll("max").data(min)
                    .join('text')
                    .text((d: any) => Utils.toRound(d.y, 2))
                    .attr('class', 'draggable')
                    .attr('font-family', font)
                    .attr('font-size', size)
                    .attr('font-weight', weight)
                    .attr("transform", (d: any) => `translate(${xScaler(d.x) + size / 3}, ${yScaler(d.y) +  (pixelWidth(Utils.toRound(d.y, 2), { font: font, bold: weight === 'bold', size: size }) + 10) }) rotate(-90)`)
                    .style("text-anchor", "start")
                    .attr("fill", chartElement['color'] ?? 'red')    
            );

        this.el.append('g').attr("data-type", "minPoints")
            .call((g: any) => 
                g.selectAll("max").data(max)
                    .join('circle')
                    .attr("cx", (d: any) => xScaler(d.x))
                    .attr("cy", (d: any) => yScaler(d.y))
                    .attr("r", chartElement['radius'] ?? 2.5)
                    .attr("fill", chartElement['color'] ?? 'red')
            )
            .call((g: any) => 
                g.selectAll("max").data(max)
                    .join('text')
                    .text((d: any) => Utils.toRound(d.y, 2))
                    .attr('class', 'draggable')
                    .attr('font-family', font)
                    .attr('font-size', size)
                    .attr('font-weight', weight)
                    .attr("transform", (d: any) => `translate(${xScaler(d.x) + size / 3}, ${yScaler(d.y) - 10 }) rotate(-90)`)
                    .style("text-anchor", "start")
                    .attr("fill", chartElement['color'] ?? 'red')    
            );

        if (chartElement['last_point']) {
            const maxPoint = Utils.getMaxPointByDate(chartData.data, 'x');

            this.el.append('g').attr("data-type", "lastPoints")
                .call((g: any) => 
                    g.append('circle')
                        .attr("cx", (xScaler(maxPoint.x)))
                        .attr("cy", (yScaler(maxPoint.y)))
                        .attr("r", chartElement['radius'] ?? 2.5)
                        .attr("fill", chartElement['color'] ?? 'red')
                )
                .call((g: any) => 
                    g.append('text')
                        .text(Utils.toRound(maxPoint.y, 2))
                        .attr('class', 'draggable')
                        .attr('font-family', font)
                        .attr('font-size', size)
                        .attr('font-weight', weight)
                        .attr("transform", `translate(${xScaler(maxPoint.x) + 10}, ${yScaler(maxPoint.y) + size / 3 })`)
                        .style("text-anchor", "start")
                        .attr("fill", chartElement['color'] ?? 'red')    
                );
        }
    }
    
    addLegendInner(chartElement: LegendInner) {
        const font = chartElement['font_family'] ?? 'times new roman';
        const size = chartElement['font_size'] ?? 12;
        const weight = chartElement['font_weight'] ?? 300;

        const height = size + 10;

        this.el.append('g').attr("transform", `translate(${chartElement.position.x}, ${chartElement.position.y})`)
            .call((g: any) => 
                g.selectAll("circle").data(chartElement['legends'])
                    .join('circle')
                    .attr("cx", 0)
                    .attr("cy", (d: any, index: number) => height * index + height / 2 - size / 2)
                    .attr("r", size / 2)
                    .attr("fill", (d: any) => d.color ?? 'red'))
            .call((g: any) => 
                g.selectAll("text").data(chartElement['legends'])
                    .join('text')
                    .text((d: any) => d.text)
                    .attr("x", size)
                    .attr("y", (d: any, index: number) => height * index + height / 2 - 2)
                    .attr('font-size', size)
                    .attr('font-family', font)
                    .attr('font-weight', 'bold')
                    .attr("fill", 'black'));
    }
}

// export type LegendInner = {
//     'type': 'LegendInner',
//     'position': { 'x': number, 'y': number };
//     'font_family'?: 'times new roman' | 'arial';
//     'font_weight'?: string; 
//     'font_size'?: number;

//     'legends': { 'color': string, 'text': string }[];
// }
